package net.gillessed.icarus.engine;


import java.awt.Color;

import net.gillessed.icarus.event.ProgressChangeEvent;
import net.gillessed.icarus.geometry.FractalPixel;

public class SuperSampleEngineThread extends EngineThread {

	private FractalEngine fractalEngine;
	
	private FractalPixel[][] supersamples;
	private Color[][] pixels;
	
	public SuperSampleEngineThread(FractalEngine engine, EngineThread nextThread) {
		super(engine, nextThread, FractalEngine.SUPERSAMPLING);
		fractalEngine = engine;
		
		supersamples = fractalEngine.getSupersamples();
		pixels = fractalEngine.getPixels();
	}

	@Override
	public void run() {
		// Log density correction
		double maximum = 0;
		for(int i = 0; i < fractalEngine.getPixelWidth(); i++)
		{
			for(int j = 0; j < fractalEngine.getPixelHeight(); j++)
			{
				double freq = supersamples[i][j].getFrequency();
				if(Math.log(freq) > maximum)
				{
					maximum = supersamples[i][j].getFrequency();
				}
				augmentProgress();
			}
		}
		double color = 0, temp = 0;
		double logmax = Math.log(maximum);
		for(int i = 0; i < fractalEngine.getPixelWidth(); i++)
		{
			for(int j = 0; j < fractalEngine.getPixelHeight(); j++)
			{
				temp = supersamples[i][j].getFrequency();
				if(temp > 0)
				{
					color = Math.log(temp) / logmax;
					if(color > 1) {
						color = 1;
					}
				}
				else
				{
					color = 0;
				}
				supersamples[i][j].setAlpha(color);
				augmentProgress();
			}
		}
		
		double kernel;
		int matrixSize;
		double[][] blurMatrix;
		Color[][] colorMatrix;
		double[][] alphaMatrix;
		int mid;
		int limit = 9;
		int lowerlimit = 3;
		
		// Guassian blur and gamma correction
		for(int i = 1; i < fractalEngine.getPixelWidth() - 1; i++) {
			for(int j = 1; j < fractalEngine.getPixelHeight() - 1; j++) {
//				kernel = 0;
				kernel = (logmax / Math.log(supersamples[i][j].getFrequency())) / 2;
				matrixSize = Math.max(0, (int)(kernel*6.0d));
				if(matrixSize > i) matrixSize = i;
				if(matrixSize > j) matrixSize = j;
				if(matrixSize > fractalEngine.getPixelWidth() - i) matrixSize = fractalEngine.getPixelWidth() - i;
				if(matrixSize > fractalEngine.getPixelHeight() - j) matrixSize = fractalEngine.getPixelHeight() - j;
				if(matrixSize % 2 == 0) matrixSize++;
				if(matrixSize > limit) matrixSize = limit;
				if(matrixSize < lowerlimit) matrixSize = lowerlimit;
				mid = (matrixSize - 1) / 2;
				blurMatrix = new double[matrixSize][matrixSize];
				colorMatrix = new Color[matrixSize][matrixSize];
				alphaMatrix = new double[matrixSize][matrixSize];
				if(matrixSize != 1) {
					double sum = 0;
					for(int ii = -mid; ii <= mid; ii++) {
						for(int jj = -mid; jj <= mid; jj++) {
							double newElem = 1 / (6.283185307 * kernel) * Math.exp(-((double)(ii*ii) + (double)(jj*jj))/(2 * kernel * kernel));
							sum += newElem;
							blurMatrix[ii+mid][jj+mid] = newElem;
							colorMatrix[ii+mid][jj+mid] = supersamples[i + ii][j + jj].getColor();
							alphaMatrix[ii+mid][jj+mid] = supersamples[i + ii][j + jj].getAlpha();
						}
					}
					for(int ii = -mid; ii <= mid; ii++) {
						for(int jj = -mid; jj <= mid; jj++) {
							blurMatrix[ii+mid][jj+mid] = blurMatrix[ii+mid][jj+mid] / sum;
						}
					}
				} else {
					blurMatrix[0][0] = 1;
					colorMatrix[0][0] = supersamples[i][j].getColor();
					alphaMatrix[0][0] = supersamples[i][j].getAlpha();
				}
				
				double r = 0;
				double g = 0;
				double b = 0;
				double a = 0;
				
				for(int ii = -mid; ii <= mid; ii++) {
					for(int jj = -mid; jj <= mid; jj++) {
						r += (double)colorMatrix[ii+mid][jj+mid].getRed() * blurMatrix[ii+mid][jj+mid];
						g += (double)colorMatrix[ii+mid][jj+mid].getGreen() * blurMatrix[ii+mid][jj+mid];
						b += (double)colorMatrix[ii+mid][jj+mid].getBlue() * blurMatrix[ii+mid][jj+mid];
						a += (double)alphaMatrix[ii+mid][jj+mid] * blurMatrix[ii+mid][jj+mid];
					}
				}
				
				if(a > 1) a = 1;
				
				double alpha = Math.pow(a, 1 / fractalEngine.getFlameModel().getGamma()) * 255.0;

				Color cAlpha = new Color((int)r,(int)g,(int)b, (int)alpha);
				
				pixels[i][j] = cAlpha;
				augmentProgress();
				blurMatrix = null;
				colorMatrix = null;
			}
		}
		fractalEngine.fireProgressChangeEvent(new ProgressChangeEvent(this, 100, true, false));
	}

	@Override
	public double getProgressTotal() {
		if(progressTotal == 0) {
			double pw = fractalEngine.getPixelWidth();
			double ph = fractalEngine.getPixelHeight();
			progressTotal = 3 * pw * ph;
		}
		return progressTotal;
	}

}
